package org.csu.mypetstore.api.utils.encrypt;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.csu.mypetstore.api.utils.encrypt.annotation.SensitiveData;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;

/**
 * 加密拦截器：插入数据库之前对敏感数据加密
 * 暂时只做了简单的 MD5 加密
 * 场景：插入、更新时生效
 * 策略：
 *   - 在敏感字段所在实体类上添加@SensitiveData注解
 *   - 在敏感字段上添加@SensitiveField注解
 *
 * @author csu_cangkui
 * @date 2021/8/14
 */
@Slf4j
@Component
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class EncryptInterceptor implements Interceptor {

    // 更新参数名称，ParamMap的key值
    private static final String CRYPT = "et";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            // @Signature 指定了 type= parameterHandler.class 后，这里的 invocation.getTarget() 便是parameterHandler
            // 若指定ResultSetHandler，这里则能强转为ResultSetHandler
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            // 获取参数对像，即 mapper 中 paramsType 的实例
            Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
            parameterField.setAccessible(true);
            // 取出参数实例
            Object parameterObject = parameterHandler.getParameterObject();
            if (parameterObject != null) {
                Object sensitiveObject = null;
                if (parameterObject instanceof MapperMethod.ParamMap) {
                    // 更新操作被拦截
                    Map paramMap = (Map) parameterObject;
                    if (paramMap.containsKey(CRYPT)) {
                        sensitiveObject = paramMap.get(CRYPT);
                    } else {
                        return invocation.proceed();
                    }
                } else {
                    // 插入操作被拦截，parameterObject即为待插入的实体对象
                    sensitiveObject = parameterObject;
                }
                // 获取不到数据就直接放行
                if (Objects.isNull(sensitiveObject)) return invocation.proceed();
                // 校验该实例的类是否被@SensitiveData所注解
                Class<?> sensitiveObjectClass = sensitiveObject.getClass();
                SensitiveData sensitiveData = AnnotationUtils.findAnnotation(sensitiveObjectClass, SensitiveData.class);
                if (Objects.nonNull(sensitiveData)) {
                    // 如果是被注解的类，则进行加密
                    //取出当前当前类所有字段，传入加密方法
                    Field[] declaredFields = sensitiveObjectClass.getDeclaredFields();
                    EncryptUtil.encrypt(declaredFields, sensitiveObject, EncryptUtil.ENCRYPT_MD5_MODE);
                }
            }
            return invocation.proceed();
        } catch (Exception e) {
            // 未作更多处理，加密失败仍然会让数据进入数据库
            log.error("加密失败", e);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 将这个拦截器接入拦截器链
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {}
}
